﻿// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

#region

using System.Net;
using System.Text.RegularExpressions;
using System.Xml.XPath;

#endregion

namespace Neto.Helpers;

/// <summary>
///     XML 注释辅助类，用于从 XML 文档中提取类型、方法、属性、字段等成员的注释信息。
///     支持加载本地 XML 文件、内存流或直接传入的 XML 字符串。
/// </summary>
public static class XmlCommentHelper
{
    // 定义引用标签的正则表达式模式，用于匹配文档中的<see/>和<paramref/>标签。
    private static readonly Regex RefTagPattern =
        new(@"<(see|paramref) (name|cref)=""([TPF]{1}:)?(?<display>.+?)"" ?/>", RegexOptions.Compiled);

    // 定义代码标签的正则表达式模式，用于匹配文档中的<c/>标签，以表示代码段。
    private static readonly Regex CodeTagPattern = new(@"<c>(?<display>.+?)</c>", RegexOptions.Compiled);

    // 定义段落标签的正则表达式模式，用于匹配文档中的<para/>标签，以表示段落。
    // 使用Singleline选项允许在一个段落中包含多行。
    private static readonly Regex ParaTagPattern =
        new(@"<para>(?<display>.+?)</para>", RegexOptions.Compiled | RegexOptions.Singleline);

    // 用于存储XPathNavigator对象的列表，这些对象代表了文档中的各个节点。
    private static readonly List<XPathNavigator> navigators = new();

    /// <summary>
    ///     在 XmlCommentHelper 类中添加静态构造函数
    /// </summary>
    static XmlCommentHelper()
    {
        LoadAll();
    }

    /// <summary>
    ///     加载当前目录下所有 .xml 文件作为文档注释源。
    /// </summary>
    private static void LoadAll()
    {
        var files = Directory.GetFiles(Directory.GetCurrentDirectory());
        foreach (var file in files)
            if (string.Equals(Path.GetExtension(file), ".xml", StringComparison.OrdinalIgnoreCase))
                Load(file);
    }

    /// <summary>
    ///     从xml中加载
    /// </summary>
    /// <param name="xmls"></param>
    public static void LoadXml(params string[] xmls)
    {
        foreach (var xml in xmls) Load(new MemoryStream(Encoding.UTF8.GetBytes(xml)));
    }

    /// <summary>
    ///     从文件中加载
    /// </summary>
    /// <param name="xmlFiles"></param>
    public static void Load(params string[] xmlFiles)
    {
        foreach (var xmlFile in xmlFiles)
        {
            var doc = new XPathDocument(xmlFile);
            navigators.Add(doc.CreateNavigator());
        }
    }

    /// <summary>
    ///     从流中加载
    /// </summary>
    /// <param name="streams"></param>
    public static void Load(params Stream[] streams)
    {
        foreach (var stream in streams)
        {
            var doc = new XPathDocument(stream);
            navigators.Add(doc.CreateNavigator());
        }
    }

    /// <summary>
    ///     获取指定类型的注释内容。
    /// </summary>
    /// <param name="type">要获取注释的类型。</param>
    /// <param name="xPath">注释节点路径，默认为 "summary"。</param>
    /// <param name="humanize">是否进行可读性优化，去除 XML 标签。</param>
    /// <returns>返回格式化后的注释文本。</returns>
    public static string GetTypeComment(Type type, string xPath = "summary", bool humanize = true)
    {
        var typeMemberName = GetMemberNameForType(type);
        return GetComment(typeMemberName, xPath, humanize);
    }

    /// <summary>
    ///     读取字段或者属性的注释
    /// </summary>
    /// <param name="fieldOrPropertyInfo">字段或者属性</param>
    /// <param name="xPath">注释路径</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetFieldOrPropertyComment(MemberInfo fieldOrPropertyInfo, string xPath = "summary",
        bool humanize = true)
    {
        var fieldOrPropertyMemberName = GetMemberNameForFieldOrProperty(fieldOrPropertyInfo);
        return GetComment(fieldOrPropertyMemberName, xPath, humanize);
    }

    /// <summary>
    ///     读取方法中的注释
    /// </summary>
    /// <param name="methodInfo">方法</param>
    /// <param name="xPath">注释路径</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetMethodComment(MethodInfo methodInfo, string xPath = "summary", bool humanize = true)
    {
        var methodMemberName = GetMemberNameForMethod(methodInfo);
        return GetComment(methodMemberName, xPath, humanize);
    }

    /// <summary>
    ///     获取方法的返回值注释内容。
    /// </summary>
    /// <param name="methodInfo">方法信息。</param>
    /// <param name="humanize">是否进行可读性优化。</param>
    /// <returns>返回格式化后的返回值注释。</returns>
    public static string GetMethodReturnComment(MethodInfo methodInfo, bool humanize = true)
    {
        return GetMethodComment(methodInfo, "returns", humanize);
    }

    /// <summary>
    ///     读取参数的注释
    /// </summary>
    /// <param name="parameterInfo">参数</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetParameterComment(ParameterInfo parameterInfo, bool humanize = true)
    {
        if (!(parameterInfo.Member is MethodInfo methodInfo)) return string.Empty;

        var methodMemberName = GetMemberNameForMethod(methodInfo);
        return GetComment(methodMemberName, $"param[@name='{parameterInfo.Name}']", humanize);
    }

    /// <summary>
    ///     读取方法的所有参数的注释
    /// </summary>
    /// <param name="methodInfo">方法</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static Dictionary<string, string> GetParameterComments(MethodInfo methodInfo, bool humanize = true)
    {
        var parameterInfos = methodInfo.GetParameters();
        var dict = new Dictionary<string, string>();
        foreach (var parameterInfo in parameterInfos)
            dict[parameterInfo.Name] = GetParameterComment(parameterInfo, humanize);
        return dict;
    }

    /// <summary>
    ///     读取指定名称节点的注释
    /// </summary>
    /// <param name="name">节点名称</param>
    /// <param name="xPath">注释路径</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetComment(string name, string xPath, bool humanize = true)
    {
        foreach (var _xmlNavigator in navigators)
        {
            var typeSummaryNode =
                _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{name}']/{xPath.Trim('/', '\\')}");

            if (typeSummaryNode != null)
                return humanize ? Humanize(typeSummaryNode.InnerXml) : typeSummaryNode.InnerXml;
        }

        return string.Empty;
    }

    /// <summary>
    ///     读取指定节点的summary注释
    /// </summary>
    /// <param name="name">节点名称</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetSummary(string name, bool humanize = true)
    {
        return GetComment(name, "summary", humanize);
    }

    /// <summary>
    ///     读取指定节点的example注释
    /// </summary>
    /// <param name="name">节点名称</param>
    /// <param name="humanize">可读性优化(比如：去掉xml标记)</param>
    /// <returns></returns>
    public static string GetExample(string name, bool humanize = true)
    {
        return GetComment(name, "example", humanize);
    }

    /// <summary>
    ///     获取方法的节点名称
    /// </summary>
    /// <param name="method">需要获取节点名称的方法信息</param>
    /// <returns>返回方法的节点名称字符串</returns>
    public static string GetMemberNameForMethod(MethodInfo method)
    {
        var builder = new StringBuilder("M:");

        // 为方法所属的类型构建节点名称
        builder.Append(QualifiedNameFor(method.DeclaringType));
        builder.Append($".{method.Name}"); // 添加方法名

        // 如果方法没有参数则直接返回构建好的字符串
        var parameters = method.GetParameters();
        if (!parameters.Any()) return builder.ToString();

        // 构建方法参数的节点名称
        var parametersNames = parameters.Select(p =>
        {
            // 如果参数是泛型参数，则使用`符号加参数位置表示，否则使用全名
            return p.ParameterType.IsGenericParameter
                ? $"`{p.ParameterType.GenericParameterPosition}"
                : QualifiedNameFor(p.ParameterType, true);
        });
        // 将参数名称用逗号连接并添加到方法名之后
        builder.Append($"({string.Join(",", parametersNames)})");

        return builder.ToString();
    }

    /// <summary>
    ///     获取类型的节点名称
    /// </summary>
    /// <param name="type">需要获取节点名称的类型信息</param>
    /// <returns>返回类型的节点名称字符串</returns>
    public static string GetMemberNameForType(Type type)
    {
        var builder = new StringBuilder("T:");
        // 为类型构建节点名称
        builder.Append(QualifiedNameFor(type));

        return builder.ToString();
    }


    /// <summary>
    ///     根据提供的成员信息（字段或属性），生成相应的成员名称字符串。
    /// </summary>
    /// <param name="fieldOrPropertyInfo">包含字段或属性信息的MemberInfo对象。</param>
    /// <returns>表示字段或属性的字符串，格式为"F:"(字段)或"P:"(属性)加上限定名称。</returns>
    public static string GetMemberNameForFieldOrProperty(MemberInfo fieldOrPropertyInfo)
    {
        // 初始化StringBuilder，根据成员类型决定是以字段(F:)还是属性(P:)开始
        var builder = new StringBuilder((fieldOrPropertyInfo.MemberType & MemberTypes.Field) != 0 ? "F:" : "P:");
        // 添加成员所属类型的限定名称
        builder.Append(QualifiedNameFor(fieldOrPropertyInfo.DeclaringType));
        // 添加成员的名称
        builder.Append($".{fieldOrPropertyInfo.Name}");

        return builder.ToString();
    }

    /// <summary>
    ///     获取给定类型的完全限定名，可选地展开泛型参数。
    /// </summary>
    /// <param name="type">要获取其完全限定名的类型。</param>
    /// <param name="expandGenericArgs">是否展开泛型参数。默认为false。</param>
    /// <returns>类型的安全完全限定名，如果类型是数组，则包括数组维度；如果类型是泛型，则可选地包括泛型参数。</returns>
    private static string QualifiedNameFor(Type type, bool expandGenericArgs = false)
    {
        // 处理数组类型
        if (type.IsArray)
            return $"{QualifiedNameFor(type.GetElementType(), expandGenericArgs)}[]";

        var builder = new StringBuilder();

        // 添加命名空间
        if (!string.IsNullOrEmpty(type.Namespace))
            builder.Append($"{type.Namespace}.");

        // 添加嵌套类型的名称
        if (type.IsNested) builder.Append($"{string.Join(".", GetNestedTypeNames(type))}.");

        // 展开构造的泛型类型及其参数
        if (type.IsConstructedGenericType && expandGenericArgs)
        {
            var nameSansGenericArgs = type.Name.Split('`').First();
            builder.Append(nameSansGenericArgs);

            var genericArgsNames = type.GetGenericArguments().Select(t =>
            {
                return t.IsGenericParameter
                    ? $"`{t.GenericParameterPosition}"
                    : QualifiedNameFor(t, true);
            });

            builder.Append($"{{{string.Join(",", genericArgsNames)}}}");
        }
        else
            builder.Append(type.Name);

        return builder.ToString();
    }

    /// <summary>
    ///     获取嵌套类型的名称序列。
    /// </summary>
    /// <param name="type">要获取嵌套类型名称的类型。</param>
    /// <returns>嵌套类型的名称序列，对于非嵌套类型，返回空序列。</returns>
    private static IEnumerable<string> GetNestedTypeNames(Type type)
    {
        // 如果类型不是嵌套的，或者没有声明类型，直接返回空序列
        if (!type.IsNested || type.DeclaringType == null) yield break;

        // 递归获取所有嵌套类型的名称
        foreach (var nestedTypeName in GetNestedTypeNames(type.DeclaringType)) yield return nestedTypeName;

        yield return type.DeclaringType.Name;
    }

    /// <summary>
    ///     对 XML 注释内容进行人性化处理，去除标签、解码 HTML 实体等。
    /// </summary>
    /// <param name="text">原始 XML 注释文本。</param>
    /// <returns>处理后的可读文本。</returns>
    private static string Humanize(string text)
    {
        ArgumentNullException.ThrowIfNull(text);

        // 对文本进行一系列处理，使其更易读
        text = NormalizeIndentation(text);
        text = HumanizeRefTags(text);
        text = HumanizeCodeTags(text);
        text = HumanizeParaTags(text);
        text = DecodeXml(text);
        return text;
    }

    /// <summary>
    ///     规范化文本的缩进。
    /// </summary>
    /// <param name="text">要规范化缩进的文本。</param>
    /// <returns>缩进被规范化后的文本。</returns>
    private static string NormalizeIndentation(string text)
    {
        // 分割文本为行，找出共同的前导空格
        var lines = text.Split('\n');
        var padding = GetCommonLeadingWhitespace(lines);

        var padLen = padding?.Length ?? 0;

        // 移除每行的前导空格
        for (int i = 0, l = lines.Length; i < l; ++i)
        {
            var line = lines[i].TrimEnd('\r'); // 移除尾部的'\r'

            if (padLen != 0 && line.Length >= padLen && line.Substring(0, padLen) == padding)
                line = line[padLen..];

            lines[i] = line;
        }

        // 移除前导空行，保留缩进，移除尾部空白字符
        return string.Join("\r\n", lines.SkipWhile(x => string.IsNullOrWhiteSpace(x))).TrimEnd();
    }

    /// <summary>
    ///     查找文本中所有行的共同前导空格。
    /// </summary>
    /// <param name="lines">要查找共同前导空格的文本行数组。</param>
    /// <returns>所有行共同的前导空格字符串，如果没有共同的前导空格，则返回null。</returns>
    private static string GetCommonLeadingWhitespace(string[] lines)
    {
        if (null == lines)
            throw new ArgumentException("lines");

        if (lines.Length == 0)
            return null;

        var nonEmptyLines = lines
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .ToArray();

        if (nonEmptyLines.Length < 1)
            return null;

        var padLen = 0;

        // 寻找所有非空行共同的前导字符
        var seed = nonEmptyLines[0];
        for (int i = 0, l = seed.Length; i < l; ++i)
        {
            if (!char.IsWhiteSpace(seed, i))
                break;

            if (nonEmptyLines.Any(line => line[i] != seed[i]))
                break;

            ++padLen;
        }

        if (padLen > 0)
            return seed[..padLen];

        return null;
    }

    /// <summary>
    ///     将引用标签（如&lt;ref&gt;）替换为其显示文本。
    /// </summary>
    /// <param name="text">包含引用标签的文本。</param>
    /// <returns>替换后的文本。</returns>
    private static string HumanizeRefTags(string text)
    {
        return RefTagPattern.Replace(text, match => match.Groups["display"].Value);
    }

    /// <summary>
    ///     将代码标签（如&lt;code&gt;）替换为花括号包围的文本。
    /// </summary>
    /// <param name="text">包含代码标签的文本。</param>
    /// <returns>替换后的文本。</returns>
    private static string HumanizeCodeTags(string text)
    {
        return CodeTagPattern.Replace(text, match => "{" + match.Groups["display"].Value + "}");
    }

    /// <summary>
    ///     将段落标签（如&lt;p&gt;）替换为&lt;br&gt;加上其显示文本。
    /// </summary>
    /// <param name="text">包含段落标签的文本。</param>
    /// <returns>替换后的文本。</returns>
    private static string HumanizeParaTags(string text)
    {
        return ParaTagPattern.Replace(text, match => "<br>" + match.Groups["display"].Value);
    }

    /// <summary>
    ///     解码XML实体，如将&amp;lt;解码为&lt;。
    /// </summary>
    /// <param name="text">要解码的文本。</param>
    /// <returns>解码后的文本。</returns>
    private static string DecodeXml(string text)
    {
        return WebUtility.HtmlDecode(text);
    }
}